/*
author: YiliangWu
实现cd相关
)普通cd。
)累积倒计时到指定数值，进入强制冷却状态
)周期特性，比如周一冷却cd。
)时间周期性增长的数值，比如游戏中每分钟加1体力。

*/


#include <time.h>
#include "time/su_timeUtil.h"
#include "time/su_timestamp.h"
#include "misc.h"
#include <stdio.h>
#include <algorithm>

using namespace su;

namespace
{

	time_t GetStartSec()
	{
		tm tmNow;
		tmNow.tm_sec = 0;
		tmNow.tm_min = 0;
		tmNow.tm_hour = 0;
		tmNow.tm_mday = 4;		//4 == week 0
		tmNow.tm_mon = 0;	//0 start
		tmNow.tm_year = 1970 - 1900;
		tmNow.tm_isdst = 0;
		return mktime(&tmNow); //不同本地时间，生成结果不一样
	}
}

namespace su
{



	TimerCnt::State TimerCnt::state()
	{
		return m_state;
	}

	void TimerCnt::start()
	{
		m_start = SysTime::Ins().Sec();
		m_state = TIMER_S_RUN;
	}

	void TimerCnt::pause()
	{
		if (TIMER_S_RUN == m_state)
		{
			m_pause = SysTime::Ins().Sec();
			m_state = TIMER_S_PAUSE;
		}
	}

	void TimerCnt::resume()
	{
		if (TIMER_S_PAUSE == m_state)
		{
			time_t cur_sec = SysTime::Ins().Sec();
			assert(cur_sec >= m_pause);
			m_start += (cur_sec - m_pause); //加上暂停时间
			m_state = TIMER_S_RUN;
		}
	}

	time_t TimerCnt::peerCurrentTimer() const
	{
		if (TIMER_S_RUN == m_state)
		{
			return SysTime::Ins().Sec() - m_start;
		}
		else if (TIMER_S_PAUSE == m_state)
		{
			return m_pause - m_start;
		}
		return time_t();
	}

	time_t TimerCnt::end()
	{
		if (TIMER_S_RUN == m_state)
		{
			m_state = TIMER_S_NULL;
			return SysTime::Ins().Sec() - m_start;
		}
		else if (TIMER_S_PAUSE == m_state)
		{ //暂停，先恢复
			m_state = TIMER_S_NULL;
			return m_pause - m_start;
		}
		return 0;
	}


	const time_t CycleNum::START_SEC = GetStartSec();

	CycleNum::CycleNum(CycleType type, const time_t& limit)
	{
		Init(type, limit);
	}

	CycleNum::CycleNum(time_t type, const time_t& limit)
	{
		Init((CycleType)type, limit);
	}

	void CycleNum::Init(CycleType type, const time_t& limit)
	{
		if (type == CycleType::MONTH)
		{
			L_ERROR("can't init CycleType::MONTH");
			return;
		}
		m_period = (time_t)type;
		m_start = START_SEC + limit;
		if (limit >= m_period)
		{
			L_ERROR("input para error, %ld", limit);
			m_start = START_SEC;
		}
	}

	time_t CycleNum::Get(const time_t& curTs) const
	{
		if (curTs < START_SEC)
		{
			return 0;
		}

		time_t totalSpan = curTs - m_start;
		time_t period_num = totalSpan / m_period;
		return period_num;
	}

	time_t CycleNum::GetCurrent() const
	{
		time_t totalSpan = SysTime::Ins().Sec() - m_start;
		return totalSpan / m_period;
	}

	time_t CycleNum::GetCurSpan() const
	{
		time_t totalSpan = SysTime::Ins().Sec() - m_start;
		return totalSpan % m_period;
	}

	time_t CycleNum::GetCurSpan(time_t curTs, CycleType period)
	{
		if (period == CycleType::MONTH)
		{
			tm t;
			localtime_r(&curTs, &t);
			return (t.tm_mday - 1)*3600 * 24 + t.tm_hour * 3600 + t.tm_min * 60 + t.tm_sec;
		}
		else
		{
			time_t total = curTs - START_SEC;
			return total % (time_t)period;
		}
	}

	time_t CycleNum::GetPeriodNum(time_t curTs, CycleType period)
	{
		if (period == CycleType::MONTH)
		{
			tm t;
			localtime_r(&START_SEC, &t);
			time_t startCnt = (t.tm_year - 70) * 12 + t.tm_mon + 1;
			localtime_r(&curTs, &t);
			time_t nowCnt = (t.tm_year - 70) * 12 + t.tm_mon + 1;
			return nowCnt - startCnt + 1;
		}
		else
		{
			if (START_SEC > curTs)
			{
				return 0;
			}

			time_t dist = curTs - START_SEC;
			return dist / (time_t)period;
		}
	}

	time_t CycleNum::GetPeriodSpan(time_t ts1, time_t ts2, CycleType period)
	{
		return GetPeriodNum(ts1, period) - GetPeriodNum(ts2, period);
	}

	CdDefault::CdDefault(const time_t& period, time_t start)
		:m_start(start)
		, m_period(period)
	{
	}

	bool CdDefault::IsCD() const
	{
		const time_t current = SysTime::Ins().Sec();
		if (current < m_start)
		{
			return true;
		}
		if (current - m_start < m_period)
		{
			return true;
		}
		return false;
	}

	time_t CdDefault::GetCountDownTime() const
	{
		const time_t current = SysTime::Ins().Sec();
		if (current < m_start)
		{
			return m_period + m_start - current;
		}
		if (current - m_start < m_period)
		{
			return m_period - (current - m_start);
		}
		return time_t();
	}

	void CdDefault::ChangePeriod(const time_t& period)
	{
		if (!IsCD())
		{
			m_start = time_t();
		}
		m_period = period;
	}



	void CdDefault::StartCD()
	{
		m_start = SysTime::Ins().Sec();
	}

	void CdDefault::ClearCD()
	{
		m_start = time_t();
	}

	PeriodAccumulate::PeriodAccumulate(const time_t& period, uint32 max_num)
		:m_num(max_num)
		, m_max_num(max_num)
		, m_last_cycle_cnt(0)
		, m_period_cnt(period)
	{
	}

	uint32 PeriodAccumulate::currentNum()
	{
		time_t cur = m_period_cnt.GetCurrent();
		time_t temp = m_last_cycle_cnt;
		m_last_cycle_cnt = cur;
		uint32 add = 0;
		if (cur > temp)
		{
			add = (uint32)(cur - temp);
		}

		if (m_num >= m_max_num)
		{
			return m_num;
		}
		m_num += add;
		if (m_num > m_max_num)
		{
			m_num = m_max_num;
		}
		return m_num;
	}

	void PeriodAccumulate::costNum(uint32 cost)
	{
		currentNum();
		if (cost > m_num)
		{
			m_num = 0;
			return;
		}
		m_num -= cost;
	}

	void PeriodAccumulate::addExtNum(uint32 add)
	{
		currentNum();
		m_num += add;
	}

	void PeriodAccumulate::setMaxNum(uint32 max_num)
	{
		m_max_num = max_num;
	}


	CycleMoreReset::CycleMoreReset(const time_t& period, const VecInt64& vec_limit)
		:m_period(period)
	{
		m_vec_limit = vec_limit;
		std::sort(m_vec_limit.begin(), m_vec_limit.end());

		for (int64 v : m_vec_limit)
		{
			if (v >= period)
			{
				L_ERROR("input para error, %ld", v);
			}
		}
	}

	time_t CycleMoreReset::GetCurCycleNum() const
	{
		time_t cur_sec = SysTime::Ins().Sec();
		if (cur_sec < CycleNum::START_SEC)
		{
			return 0;
		}

		time_t dist_sec = cur_sec - CycleNum::START_SEC;
		time_t major_period = dist_sec / m_period;
		time_t remainder = dist_sec % m_period;

		time_t add = 0;
		for (VecInt64::const_iterator it = m_vec_limit.begin(); it != m_vec_limit.end(); ++it)
		{
			if (remainder >= *it)
			{
				++add;
			}
		}

		return major_period * m_vec_limit.size() + add;
	}

	void CycleTimer::Start(CycleType cycleType, TimerCB f, time_t startSpan)
	{
		L_COND_V(startSpan < (time_t)cycleType);
		m_cycleType = cycleType;
		m_f = f;
		m_timeOutSpan = startSpan;
		StartExactly();
	}

	void CycleTimer::OnTimer()
	{
		m_f();
		StartExactly();
	}

	void CycleTimer::Stop()
	{
		m_timer.Stop();
	}

	bool CycleTimer::IsTiming()
	{
		return m_timer.IsTiming();
	}

	uint32 CycleTimer::GetTimerTotalSec() const
	{
		return m_timerTotalSec;
	}

	void CycleTimer::StartExactly()
	{
		time_t curSpan = CycleNum::GetCurSpan(SysTime::Ins().Sec(), m_cycleType);
		if (curSpan < m_timeOutSpan)
		{
			m_timerTotalSec = (uint32_t)(m_timeOutSpan - curSpan);
			m_timer.Start(m_timerTotalSec, &CycleTimer::OnTimer, this);
		}
		else
		{
			m_timerTotalSec = (uint32_t)((time_t)m_cycleType - (curSpan - m_timeOutSpan));
			m_timer.Start(m_timerTotalSec, &CycleTimer::OnTimer, this);
		}
	}

	bool CheckSpanInCycle::Init(CycleType crycleType, time_t startSpanSec, time_t endSpanSec)
	{
		L_COND_F(startSpanSec < (time_t)crycleType);
		L_COND_F(endSpanSec < (time_t)crycleType);
		if (0 != endSpanSec)
		{
			L_COND_F(startSpanSec < endSpanSec);
		}

		m_crycleType = crycleType;
		m_startSec = startSpanSec;
		m_endSec = endSpanSec;
		return true;
	}

	bool CheckSpanInCycle::InitMonthType(int startDay, time_t starDaySpan, int endDay, time_t endDaySpan)
	{
		m_crycleType = CycleType::MONTH;
		m_startSec = (startDay - 1) * 3600 * 24 + starDaySpan;
		m_endSec = (endDay - 1) * 3600 * 24 + endDaySpan;

		L_COND_F(startDay >= 1);
		if (0 != m_endSec)
		{
			L_COND_F(endDay >= 1);
			L_COND_F(startDay <= endDay);
			L_COND_F(m_startSec < m_endSec);
		}
		L_COND_F(m_startSec < 31 * 3600 * 24);
		if (0 != m_endSec)
		{
			L_COND_F(m_endSec < 31 * 3600 * 24);
		}
		return true;
	}

	bool CheckSpanInCycle::IsOpen() const
{
		time_t curSec = CycleNum::GetCurSpan(SysTime::Ins().Sec(), m_crycleType);

		if (0 == m_endSec)
		{
			return curSec >= m_startSec;
		}
		return curSec >= m_startSec && curSec <= m_endSec;
	}

	bool SpanInCycle::Init(CycleType cycleType, time_t startSec, time_t endSec, bool isMidWayOpen /*= false*/)
	{
		L_COND_F(startSec < (time_t)cycleType);
		L_COND_F(endSec < (time_t)cycleType);
		L_COND_F(startSec < endSec);

		m_startTimer.Stop();
		m_endTimer.Stop();
		m_startTimer.Start(cycleType, &SpanInCycle::OnTimerStart, this, startSec);
		m_endTimer.Start(cycleType, &SpanInCycle::OnTimerEnd, this, endSec);
		if (m_startTimer.GetTimerTotalSec() > m_endTimer.GetTimerTotalSec()) //中途创建
		{
			if (isMidWayOpen)
			{
				OnTimerStart();
			}
		}
		return true;
	}

	void SpanInCycle::OnTimerStart()
	{
		OnStart();
		m_isOpen = true;
	}
	
	void SpanInCycle::OnTimerEnd()
	{
		if (m_isOpen)
		{
			OnEnd();
		}
		m_isOpen = false;
	}

}